package net.flowas.genericcrud.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;

/**
 * 数据库事物代理.
 * <p>只能代理接口和实现分离的实现,并且@Transactional注解应该写在接口类中.</p>
 * @author liujh
 *
 */
public class TransactionProxy {

  private static TransactionProxy instance = new TransactionProxy();
  
  private TransactionProxy(){}
  
  public static TransactionProxy getInstance() {
    return instance;
  }
  
  private EntityManager em;
  
  public void setEntityManager(EntityManager em) {
    this.em = em;
  }
  
  /**
   * 创建代理类.
   * @param object 被代理的对象
   * @return
   */
  public <T> T createProxy(T object) {
	 Class<?>[] interfases = object.getClass().getInterfaces();
	 Class<? extends Object> superClass = object.getClass().getSuperclass();
	 while(superClass != Object.class) {
		 Class<?>[] superInterfaces = superClass.getInterfaces();
		 Class<?>[] result = Arrays.copyOf(interfases, interfases.length + superInterfaces.length);  
		 System.arraycopy(superInterfaces, 0, result, interfases.length, superInterfaces.length);
		 interfases = result;
		 superClass = superClass.getSuperclass();
	 }
	TransactionHandler handler = new TransactionHandler(object);
    Object proxy = Proxy.newProxyInstance(object.getClass().getClassLoader(),
    		interfases, handler);
    return (T) proxy;
  }

  private class TransactionHandler implements InvocationHandler {
    private Object target;

    public TransactionHandler(Object target) {
      this.target = target;
    }

    @Override
    public synchronized Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Transactional transactional = method.getAnnotation(Transactional.class);
      Object result;
      if (transactional != null) {
        try {
          em.getTransaction().begin();
          result = method.invoke(target, args);
          em.getTransaction().commit();
        } catch (Exception e) {
          em.getTransaction().rollback();
          throw e;
        }        
      } else {
        result = method.invoke(target, args);
      }
      return result;
    }
  }
}
